<html>
<head>
<title>OpenLayers + MapQuest</title>
<script src="http://www.openlayers.org/api/OpenLayers.js"></script>
<script type="text/javascript">
	
	var baseFeatureUrl = "http://open.micello.com/mapdata/featurecollection/linkedmap/";
	var baseIndoorUrl = "http://open.micello.com/openindoor/map.html?mapid=";
	
    var map;
    var arrayOSM;
    var arrayAerial;
    var baseOSM;
    var baseAerial;
	var vectorLayer;
	var selectControl;
	
	var tileCache = {};
	var activeTiles = [];
	
	var proj900913 = new OpenLayers.Projection("EPSG:900913");
    var projWGS84 = new OpenLayers.Projection("EPSG:4326");
	
	var selection;
	var popup;
	
	var TILE_ZOOM = 13;
	var MIN_FEATURE_ZOOM = 15;
	
	var DEFAULT_LON = -117.1068544;
	var DEFAULT_LAT = 40.3813539;
	var DEFAULT_ZOOM = MIN_FEATURE_ZOOM;
	
	
	var HALF_METERS = 20037508.34;
	
    function init(){
		//create map
        map = new OpenLayers.Map('map', {
			eventListeners: {
				"moveend": onMapMoved
			}
		});
		
		//create base layer
        arrayOSM = ["http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.jpg",
			"http://otile2.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.jpg",
			"http://otile3.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.jpg",
			"http://otile4.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.jpg"];
        arrayAerial = ["http://oatile1.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg",
			"http://oatile2.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg",
			"http://oatile3.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg",
			"http://oatile4.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.jpg"];
        
        baseOSM = new OpenLayers.Layer.OSM("MapQuest-OSM Tiles", arrayOSM);
        baseAerial = new OpenLayers.Layer.OSM("MapQuest Open Aerial Tiles", arrayAerial);
		
		//create overlay layer
		var styleMap = getStyleMap();
		vectorLayer = new OpenLayers.Layer.Vector("Overlays", {
			styleMap: styleMap,
			"sphericalMercator": true
		});
		
		//click on overlays
		vectorLayer.events.on({
			'featureselected':onFeatureSelect,
			'featureunselected':onFeatureUnselect
		});
		
		popup = new OpenLayers.Popup.FramedCloud("featurePopup",
		null,
		new OpenLayers.Size(100,100),
		"",
		null, true, this.onPopupClose);
		
		//set up map
        map.addLayer(baseOSM);
        map.addLayer(baseAerial);
		map.addLayer(vectorLayer);
        map.addControl(new OpenLayers.Control.LayerSwitcher());
		
		selectControl = new OpenLayers.Control.SelectFeature(vectorLayer);
		map.addControl(selectControl);
		selectControl.activate();
		
		//position map
		var lon = parseFloat(getQueryParam("lon"));
		var lat = parseFloat(getQueryParam("lat"));
		var zoom = parseInt(getQueryParam("zoom"));
		if((!lat)||(!lon)) {
			lon = DEFAULT_LON;
			lat = DEFAULT_LAT;
		}
		if(!zoom) {
			zoom = DEFAULT_ZOOM;
		}
		
        map.setCenter(
			new OpenLayers.LonLat(lon,lat).transform(
				new OpenLayers.Projection("EPSG:4326"),map.getProjectionObject()), zoom);    
    }
	
	function getStyleMap() {
		var normalStyle =  new OpenLayers.Style({
			"strokeColor": "#0000a0", 
			"strokeWidth": 2, 
			"strokeOpacity":.5,
			"fillColor":"#6060a0",
			"fillOpacity":.5
		});
		var selectStyle = new OpenLayers.Style({
			"strokeColor": "#a0a000", 
			"strokeWidth": 2, 
			"strokeOpacity":.5,
			"fillColor":"#a0a040",
			"fillOpacity":.5
		});
		var styleMap = new OpenLayers.StyleMap({
			"default":normalStyle,
			"select":selectStyle
		});
		var ruleLabel = new OpenLayers.Rule({
			"filter": new OpenLayers.Filter.Comparison({
				"type": OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
				"property": "name",
				"value": null
			}),
			"symbolizer": {
				"label": "${name}",
				"fontColor": "#000000",
				"fontOpacity": 1,
				"fontFamily": "Arial",
				"fontSize": 16,
				"fontWeight": "600"
			}
		});
		normalStyle.addRules([ruleLabel]);
		selectStyle.addRules([ruleLabel]);
		return styleMap;
	}
	
	function getQueryParam(key) {
		var query = location.search;
		if(query.length > 1) {
			//get rid of '?'
			query = query.substr(1);
		}
		var pairs = query.split('&');
		for(var i = 0; i < pairs.length; i++) {
			var pairString = pairs[i];
			var pairArray = pairString.split('=');
			if((pairArray.length == 2)&&(pairArray[0] == key)) return pairArray[1];
		}
		return null;
	}
	
	//---------------------
	//select event handling
	//---------------------
	
	function onPopupClose(evt) {
		selectControl.unselect(selection);
	}
	
	function onFeatureSelect(evt) {
		var feature = evt.feature;
		
		var url = baseIndoorUrl + feature.fid;
		var html = "<h3>" + feature.data.name + "</h3>";
		html += '<input type="button" value="Open Map" onclick="window.open(\'' + url + '\')">';
		
		selection = feature;
		popup = new OpenLayers.Popup.FramedCloud("featurePopup",
		feature.geometry.getBounds().getCenterLonLat(),
		new OpenLayers.Size(30,30),
		html,
		null, true, onPopupClose);
		map.addPopup(popup);
	}
	function onFeatureUnselect(evt) {
		selection = null;
		if (popup) {
			map.removePopup(popup);
			popup.destroy();
			popup = null;
		}
	}
	
	//---------------------
	//overlay tile handling
	//---------------------
	
	function onMapMoved(event) {
		var zoom = map.getZoom();
		
		var minX;
		var maxX;
		var minY;
		var maxY;
		if(zoom >= MIN_FEATURE_ZOOM) {
			//calculate the needed tiles
			var bounds = map.calculateBounds();
			minX = xMetersToZoomCoords(bounds.left,TILE_ZOOM);
			maxX = xMetersToZoomCoords(bounds.right,TILE_ZOOM);
			minY = yMetersToZoomCoords(bounds.top,TILE_ZOOM);
			maxY = yMetersToZoomCoords(bounds.bottom,TILE_ZOOM);
		}
		else {
			//clear the map
			minX = 0;
			maxX = -1;
			minY = 0;
			maxY = -1;
		}
		
		var found;
		var tile;
		
		//remove unneeded tiles
		for(var it = 0; it < activeTiles.length; ) {
			tile = activeTiles[it];
			if((tile.x <minX)||(tile.x > maxX)||(tile.y <minY)||(tile.y > maxY)) {
				//remove tile, do not increment counter
				removeTile(it);
			}
			else {
				//increment counter
				it++;
			}
		}
		//load needed tiles
		for(var ix = minX; ix <= maxX; ix++) {
			for(var iy = minY; iy <= maxY; iy++) {
				found = false;
				for(var it = 0; it < activeTiles.length; it++) {
					tile = activeTiles[it];
					if((tile.x == ix)&&(tile.y == iy)) {
						found = true;
						break;
					}
				}
				if(!found) {
					addTile(ix,iy);
				}
			}
		}
	}
	
	function removeTile(index) {
		var tile = activeTiles[index];
		tile.active = false;
		activeTiles.splice(index,1);
		if(tile.features != null) {
			vectorLayer.removeFeatures(tile.features);
		}
	}
	
	function addTile(x,y) {
		//fetch tile
		var key = getTileKey(x,y);
		var tile = tileCache[key];
		if(tile == null) {
			tile = {};
			tile.x = x;
			tile.y = y;
			tile.features = null;
			tile.active = false;
			tileCache[key] = tile;
			//request the tile
			loadTile(tile);
		}
		//activate tile
		tile.active = true;
		activeTiles.push(tile);
		if(tile.features != null) {
			vectorLayer.addFeatures(tile.features);
		}
	}
	
	function loadTile(tile) {
		var config = {};
		config.url = baseFeatureUrl + TILE_ZOOM + "/" + tile.x + "/" + tile.y;
		var d = new Date();
		config.url = config.url + "?t=" + d.getTime();
		config.callback = onTileDownload;
		var xmlhttp = OpenLayers.Request.GET(config);
	}
	
	function onTileDownload(xmlhttp) {
		var data = eval('(' + xmlhttp.responseText + ')');
		var key = getTileKey(data.tileX,data.tileY);
		var tile = tileCache[key];
		if(tile) {
			var parser = new OpenLayers.Format.GeoJSON();		
			tile.features = parser.read(data);
			transformFeatures(tile.features);
			if(tile.active) {
				vectorLayer.addFeatures(tile.features);
			}
		}
	}
	
	function transformFeatures(features) {
		var cnt = features.length;
		for(var i = 0; i < cnt; i++) {
			features[i].geometry.transform(projWGS84,proj900913);
		}
	}
	
	function getTileKey(x,y) {
		return x + "," + y; 
	}
	
	function xMetersToZoomCoords(meters,zoom) {
		return Math.floor(Math.pow(2,zoom) * (meters + HALF_METERS)/(2 * HALF_METERS));
	}
	function yMetersToZoomCoords(meters,zoom) {
		return Math.floor(Math.pow(2,zoom) * (HALF_METERS - meters)/(2 * HALF_METERS));
	}
	
</script>
</head>

<body onload="init()">
    <div id="map"></div>
</body>
</html>